﻿/*
    Nova Code - A script language, parser and recompiler for implementing emulated code instructions
    Copyright (C) 2012  Bryan Perris

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using Nova.Parsing.GoldParser.Grammar;
using Nova.Parsing.GoldParser.Semantic;
using System.Reflection;
using Nova.Parsing.GoldParser.Parser;
using System.Collections.ObjectModel;
using Nova.Parsing.Constructs;

namespace Nova.Parsing
{
    public class NovaParser
    {
        // Parser State
        private bool m_IsParsing = false;
        private bool m_ThreadRunning = false;
        private bool m_Initialized = false;

        // Gold Parser State
        private SemanticTypeActions<NovaToken> m_Actions;

        // Parser Events
        public event EventHandler<ParseCompleteEventArgs> OnParseComplete;
        public event EventHandler<ParseErrorEventArgs> OnParseError;

        public NovaParser()
        {
        }

        public void Initialize()
        {
            // Get a stream of the grammar file stored in the assembly
            Stream grammerStream = typeof(NovaParser).Assembly.GetManifestResourceStream("Nova.Parsing.NovaGrammar.cgt");

            // Throw exception if grammarStream is null
            if (grammerStream == null)
                throw new ParserInitializeException("Failed to grab the grammar resource! Wrong path?\nUse ildasm to find out the qualified resource name.");

            // Load the compiled grammar table
            CompiledGrammar grammar = CompiledGrammar.Load(grammerStream);
            m_Actions = new SemanticTypeActions<NovaToken>(grammar);

            // Initialize the actions
            try
            {
                m_Actions.Initialize(true);
            }
            catch (InvalidOperationException e)
            {
                throw new ParserInitializeException("Failed to initialize the parser", e);
            }

            m_Initialized = true; 
        }

        public void Parse(FileStream codeSourceStream)
        {
            Parse(codeSourceStream, false);
        }

        public void Parse(Stream codeSourceStream, bool debug)
        {
            CheckOperationState();
            m_IsParsing = true;
            SemanticProcessor<NovaToken> processor = new SemanticProcessor<NovaToken>(new StreamReader(codeSourceStream), m_Actions);
            ParseMessage message = ParseMessage.None;

            if (!debug)
            {
                message = processor.ParseAll();
            }
            else
            {
                while (message <= ParseMessage.Reduction || message >= ParseMessage.CommentBlockRead)
                {
                    message = processor.Parse();

                    Console.WriteLine("Read Token \'{0}\'", processor.CurrentToken);
                }
            }

            if (message == ParseMessage.CommentError || message == ParseMessage.InternalError ||
                message == ParseMessage.LexicalError || message == ParseMessage.SyntaxError)
            {
                if (message == ParseMessage.SyntaxError)
                {
                    StringBuilder expectedTokens = new StringBuilder();

                    foreach (Symbol token in processor.GetExpectedTokens())
                    {
                        expectedTokens.Append(token.Name + ", ");
                    }

                    // Remove the last comma and space
                    if (expectedTokens.Length > 0)
                        expectedTokens.Remove(expectedTokens.Length - 2, 2);

                    OnEvent_ParseError(
                        String.Format("The parser has ran into an error: parse @ {0} has expected token(s): ({1})",
                        ((IToken)processor.CurrentToken).Position,
                        expectedTokens.ToString()));
                }
                else
                {
                    OnEvent_ParseError("The parser has ran into an error: " + message.ToString());
                }
            }
            else
            {
                if (message == ParseMessage.Accept)
                {
                    OnEvent_ParseComplete((InstructionSetConstruct)processor.CurrentToken);
                }
            }

            m_IsParsing = false;
        }

        private void PrintCodeTree(NovaToken novaToken)
        {
            InstructionSetConstruct instructionBlocks = novaToken as InstructionSetConstruct;

            foreach (InstructionBlockConstruct block in instructionBlocks)
            {
                Console.WriteLine(String.Format("+| Instruction Begin {0}: ", block.InstructionName));

                foreach (StatementConstruct statement in block.Statements)
                {
                    Console.WriteLine("+| \t Statement: " + statement.ToString());
                }

                Console.WriteLine("+| Instruction End\n");
            }
        }

        public void BeginParse(Stream codeSourceStream, object state)
        {

        }

        public void EndParse(object state)
        {

        }

        private void CheckOperationState()
        {
            if (!m_Initialized)
                throw new InvalidOperationException("The parser has not been initialized yet.  Please call Initialize() first.");

            if (m_IsParsing)
                throw new InvalidOperationException("The parser is already running.");
        }

        protected virtual void OnEvent_ParseComplete(InstructionSetConstruct set)
        {
            if (OnParseComplete != null)
                OnParseComplete(this, new ParseCompleteEventArgs(set));
        }

        protected virtual void OnEvent_ParseError(string errorMessage)
        {
            if (OnParseError != null)
                OnParseError(this, new ParseErrorEventArgs(errorMessage));
        }
    }
}
